// --------------------------------------------------------------------------------------------------------------------
// <copyright file="FileProxyUnitTest.cs" company="Microsoft Corporation">
// The MIT License (MIT)
// 
// Copyright (c) 2014, Microsoft Corporation
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//  THE SOFTWARE.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
namespace Test.Robotics.Runtime
{
    using System;
    using System.IO;
    using System.Reflection;
    using System.Threading;
    using Microsoft.Robotics.Runtime;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Basic unit test for file proxy
    /// </summary>
    [TestClass]
    public class FileProxyUnitTest
    {
        /// <summary>
        /// Simple validation of FileRecorderProxy and FilePlayerProxy with binary serialization
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void FileRecorderAndPlayerProxyAgentBinarySerializationTest()
        {
            FileProxyTest(true);
        }

        /// <summary>
        /// Simple validation of FileRecorderProxy and FilePlayerProxy with xml serialization
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void FileRecorderAndPlayerProxyAgentXMLSerializationTest()
        {
            FileProxyTest(false);
        }

        /// <summary>
        /// Simple validation of FileRecorderProxy and FilePlayerProxy in offline mode (compression scenario)
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void FileRecorderAndPlayerOffline()
        {
            string filePath = Path.GetRandomFileName();
            string compressedFilePath = Path.GetRandomFileName();
            int count = 20;

            // first generate an uncompressed file
            AgentLocator agentLocator = new AgentLocator();
            MessageInjectorAgent<SimpleAgentMessage<int[]>> injector = new MessageInjectorAgent<SimpleAgentMessage<int[]>>("injector");
            agentLocator.Register(injector);

            FileRecorderProxy recorder = new FileRecorderProxy(
                name: "recorder",
                messageTypes: new[] { typeof(SimpleAgentMessage<int[]>) },
                filePath: filePath,
                producer: injector.Name,
                binarySerialization: false,
                enableArrayCompression: false);
            IAgentHost recorderHost = agentLocator.Register(recorder);
            recorderHost.Activate();

            // write some messages, two more than the desired count because the last one might not make it in the first write and the one before it might be lost during compression
            int[] msgContent = new int[2048];
            for (int i = 0; i <= count + 1; i++)
            {
                SimpleAgentMessage<int[]> msg = new SimpleAgentMessage<int[]>(msgContent, 0);
                injector.Publish(msg);
            }

            recorderHost.Deactivate();

            // we now have at least count+1 messages in the file (the last one might or might not have made it).
            // play back the file and compress it. The locator needs to be running in offline mode to prevent message loss.
            agentLocator = new AgentLocator(true);
            
            // create the timer
            TimerAgent timer = new TimerAgent("FileTimer", 1);
            agentLocator.Register(timer);
            FilePlayerProxy player = new FilePlayerProxy(
                name: "player",
                filePath: filePath,
                messageTypes: new[] { typeof(SimpleAgentMessage<int[]>) },
                timerAgent: timer,
                binarySerialization: false);
            agentLocator.Register(player);

            recorder = new FileRecorderProxy(
                name: "recorder",
                messageTypes: new[] { typeof(SimpleAgentMessage<int[]>) },
                filePath: compressedFilePath,
                producer: player.Name,
                binarySerialization: false,
                enableArrayCompression: true);
            recorderHost = agentLocator.Register(recorder);
            recorderHost.Activate();

            if (!player.Wait(1000))
            {
                throw new Exception("Player proxy timed out.");
            }

            recorderHost.Deactivate();

            // verify that the file is smaller
            FileInfo f1 = new FileInfo(filePath);
            FileInfo f2 = new FileInfo(compressedFilePath);
            Assert.IsTrue(f1.Length > f2.Length); 

            // replay the compressed file and check that we got at least "count" messages
            agentLocator = new AgentLocator();
            agentLocator.Register(timer);
            player = new FilePlayerProxy(
                name: "player",
                filePath: compressedFilePath,
                messageTypes: new[] { typeof(SimpleAgentMessage<int[]>) },
                timerAgent: timer,
                binarySerialization: false);
            agentLocator.Register(player);

            MessageCollectorAgent<SimpleAgentMessage<int[]>> collectorAgent = new MessageCollectorAgent<SimpleAgentMessage<int[]>>("collector", player.AsProducer<SimpleAgentMessage<int[]>>(), count);
            IAgentHost collector = agentLocator.Register(collectorAgent);
            collector.Activate();

            if (!collectorAgent.Wait(1000))
            {
                Assert.Fail(string.Format("Did not receive the expected number of messages on playback: {0}. Received {1} messages. ", count, collectorAgent.Results.Count));
            }

            collector.Deactivate();
        }

        /// <summary>
        /// Simple validation of FileRecorderProxy and FilePlayerProxy for the given serialization type
        /// </summary>
        /// <param name="binarySerialization">Value indicating whether to use binary serialization or xml serialization</param>
        private static void FileProxyTest(bool binarySerialization)
        {          
            string filePath = Path.GetRandomFileName();
            int count = 20;

            AgentLocator producerAgentSetLocator = new AgentLocator();

            // publish a known number of messages, waiting after each one for the consumer to pick them up
            SimpleProducerAgent producerAgent = new SimpleProducerAgent("ProducerAgent", count, 0);
            IAgentHost producer = producerAgentSetLocator.Register(producerAgent);

            FileRecorderProxy recorderProxy = new FileRecorderProxy(
                name: "SimpleRecorder",
                messageTypes: new[] { typeof(SimpleAgentMessage<int>) },
                filePath: filePath,
                producer: producerAgent.Name,
                binarySerialization: binarySerialization);
            IAgentHost recorder = producerAgentSetLocator.Register(recorderProxy);
            recorder.Activate();

            if (!producerAgent.Wait(3000))
            {
                throw new Exception("ProducerAgent agent timed out");
            }

            // when deactivating the recorder, it is possible that the last message was not yet processed
            // rather than introducing a wait here, we'll allow for one missing message in the result set
            recorder.Deactivate();

            AgentLocator consumerAgentSetLocator = new AgentLocator();
            TimerAgent timer = new TimerAgent("FileTimer", 1);
            consumerAgentSetLocator.Register(timer);
            
            FilePlayerProxy player = new FilePlayerProxy(
                name: "player",
                filePath: filePath,
                messageTypes: new[] { typeof(SimpleAgentMessage<int>) },
                timerAgent: timer,
                binarySerialization: binarySerialization);
            consumerAgentSetLocator.Register(player);

            MessageCollectorAgent<SimpleAgentMessage<int>> subscriberAgent2 = new MessageCollectorAgent<SimpleAgentMessage<int>>(
                "Subscriber",
                player.AsProducer<SimpleAgentMessage<int>>(),
                count - 1);
            IAgentHost subscriber2 = consumerAgentSetLocator.Register(subscriberAgent2);
            subscriber2.Activate();

            // wait for a couple of timer cycles and no exception indicates success
            if (!subscriberAgent2.Wait(3000))
            {
                FileInfo info = new FileInfo(filePath);
                Assert.Fail(
                    string.Format(
                    "Did not receive the expected number of messages on playback: {0}. Received {1} messages. File size {2}, path: {3}", 
                    count - 1, 
                    subscriberAgent2.Results.Count,
                    info.Length,
                    info.Name));
            }

            subscriber2.Deactivate();        
        }
    }
}   
